#include <inttypes.h>
#include <stdio.h>
#include <string.h>
#include "esp_log.h"
#include "freertos/FreeRTOS.h"
#include "freertos/event_groups.h"
#include "esp_system.h"
#include "esp_wifi.h"
#include "esp_event_loop.h"
#include "esp_log.h"

#include "lwip/err.h"
#include "lwip/sys.h"

#define MAXIMUM_RETRY 5
#define halWifiApStop esp_wifi_stop

#include "../__generated/glue_wifi.h"

static EventGroupHandle_t wifi_event_group;
static int retry_num = 0;
const int WIFI_CONNECTED_BIT = BIT0;
char STA_HOSTNAME[17] = "QuickMCU";

/**
 * ESP32 WiFi event handler.
 *
 * SYSTEM_EVENT_AP_PROBEREQRECVED
 * SYSTEM_EVENT_AP_STACONNECTED
 * SYSTEM_EVENT_AP_STADISCONNECTED
 * SYSTEM_EVENT_AP_START
 * SYSTEM_EVENT_AP_STOP
 * SYSTEM_EVENT_STA_AUTHMODE_CHANGE
 * SYSTEM_EVENT_STA_CONNECTED
 * SYSTEM_EVENT_STA_DISCONNECTED
 * SYSTEM_EVENT_STA_GOT_IP
 * SYSTEM_EVENT_STA_START
 * SYSTEM_EVENT_STA_STOP
 * SYSTEM_EVENT_SCAN_DONE
 * SYSTEM_EVENT_WIFI_READY
 */
static esp_err_t wifi_event_handler(void *ctx, system_event_t *event)
{
    switch(event->event_id) {
        case SYSTEM_EVENT_STA_START:
            break;
        case SYSTEM_EVENT_STA_GOT_IP:
            printf("sta ip: %s\n",ip4addr_ntoa(&event->event_info.got_ip.ip_info.ip));
            retry_num = 0;
            xEventGroupSetBits(wifi_event_group, WIFI_CONNECTED_BIT);
            break;
        case SYSTEM_EVENT_STA_DISCONNECTED:
            {
                xEventGroupClearBits(wifi_event_group, WIFI_CONNECTED_BIT);
                if (retry_num < MAXIMUM_RETRY) {
                    esp_wifi_connect();
                    retry_num++;
                    printf("retry to connect to the AP\n");
                }
                printf("Disconnect!\n");
                break;
            }
        case SYSTEM_EVENT_AP_STACONNECTED:
            break;
        case SYSTEM_EVENT_AP_STADISCONNECTED:
            break;
        default:
            break;
    }
    return ESP_OK;
}

/* init */
int32_t halWifiOn()
{
    esp_log_level_set("wifi", ESP_LOG_WARN);
    wifi_event_group = xEventGroupCreate();
    tcpip_adapter_init();
    esp_err_t ret = esp_event_loop_init(wifi_event_handler, NULL);
    if(ret != ESP_OK)
    {
        return ret;
    }
    wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
    ret = esp_wifi_init(&cfg);
    if(ret != ESP_OK)
    {
        return ret;
    }
    ret = esp_wifi_set_storage(WIFI_STORAGE_RAM);
    if(ret != ESP_OK)
    {
        return ret;
    }
    ret = esp_wifi_set_mode(WIFI_MODE_NULL);
    return ret;
}

/* stop wifi & remove wifi driver */
int32_t halWifiOff()
{
    esp_err_t ret = esp_wifi_stop();
    if(ret != ESP_OK)
    {
        return ret;
    }
    ret = esp_wifi_deinit();
    return ret;
}

int32_t halWifiSetMode(uint32_t mode)
{
    esp_wifi_stop(); /* try to stop wifi in order to make change the mode easier */
    wifi_mode_t wifi_mode = (wifi_mode_t)mode;
    esp_err_t ret = esp_wifi_set_mode(wifi_mode);
    return ret;
}

/* 
 * Get current operating mode of WiFi. 
 * return WIFI_MODE_NULL, if WiFi is not initialized.
 */
wifi_mode_t halWifiGetMode()
{
    wifi_mode_t mode;
    esp_err_t ret = esp_wifi_get_mode(&mode);
    if(ret != ESP_OK)
    {
        mode = WIFI_MODE_NULL;
    }
    return mode;
}

/* start WiFi, change hostname, set config & connect to the ap */
int32_t halWifiStaConnect(const char* ssid, const char* pass)
{
    esp_err_t ret = esp_wifi_start();
    if(ret != ESP_OK)
    {
        return ret;
    }
    tcpip_adapter_set_hostname(TCPIP_ADAPTER_IF_STA, STA_HOSTNAME);
    wifi_config_t wifi_config = { 0 };
    strncpy((char*) wifi_config.sta.ssid, ssid, sizeof(wifi_config.sta.ssid));
    if (strcmp(pass,"undefined"))
    {
        strncpy((char*) wifi_config.sta.password, pass, sizeof(wifi_config.sta.password));
    }
    ret = esp_wifi_set_config(ESP_IF_WIFI_STA, &wifi_config);
    if(ret != ESP_OK)
    {
        return ret;
    }
    ret = esp_wifi_connect();
    return ret;
}

int32_t halWifiStaDisconnect()
{
    retry_num = MAXIMUM_RETRY;
    esp_err_t ret = esp_wifi_disconnect();
    if(ret != ESP_OK)
    {
        return ret;
    }
    ret = esp_wifi_stop();
    return ret;
}

/* Default: auth=open max=5 */
int32_t halWifiApStart(const char* ssid, const char* pass, uint32_t auth, uint32_t max)
{
    wifi_auth_mode_t auth_mode = (wifi_auth_mode_t)auth;
    wifi_config_t wifi_config = { 0 };
    strncpy((char*) wifi_config.ap.ssid, ssid, sizeof(wifi_config.ap.ssid));
    if (strcmp(pass,"undefined"))
    {
        strncpy((char*) wifi_config.ap.password, pass, sizeof(wifi_config.ap.password));
        wifi_config.ap.authmode = auth_mode;
    }else{
        wifi_config.ap.authmode = WIFI_AUTH_OPEN;
    }
    if (max)
    {
        wifi_config.ap.max_connection = max;
    }else{
        wifi_config.ap.max_connection = 5;
    }

    esp_err_t ret = esp_wifi_set_config(ESP_IF_WIFI_AP, &wifi_config);
    if(ret != ESP_OK)
    {
        return ret;
    }
    ret = esp_wifi_start();
    return ret;
}

void halWifiSetStaHostname(const char* name)
{
    strncpy(STA_HOSTNAME, name, sizeof(STA_HOSTNAME));
    STA_HOSTNAME[sizeof(STA_HOSTNAME)-1] = '\0'; 
}

/*----------------------------------------------------------------
int32_t halWifiSetProtocol(uint32_t protocol)
{
    wifi_interface_t ifx = ESP_IF_WIFI_STA;
    wifi_mode_t mode = halgetMode();
    if(mode == WIFI_MODE_AP)
    {
        ifx = ESP_IF_WIFI_AP;
    }
    esp_err_t ret = esp_wifi_set_protocol(ifx, protocol);
    return ret;
}
----------------------------------------------------------------*/

/*
 *  TODO:
 *    STA+AP
 *    Scan
 *    Power Save
 */